/*!	 synfig/rendering/software/task/taskmeshsw.cpp
**	 TaskMeshSW
**
**	......... ... 2015 Ivan Mahonin
**
**	This package is free software; you can redistribute it and/or
**	modify it under the terms of the GNU General Public License as
**	published by the Free Software Foundation; either version 2 of
**	the License, or (at your option) any later version.
**
**	This package is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**	General Public License for more details.
**
*/

#ifdef USING_PCH
#	include "pch.h"
#else
#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#ifndef _WIN32
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#endif

#include "taskmeshsw.h"

#include "../surfacesw.h"

#endif

using namespace synfig;
using namespace rendering;

class TaskMeshSW::Internal
{
public:
    struct IntVector {
        union {
            struct {
                int x, y;
            };
            int coords[2];
        };

        inline IntVector(): x(0), y(0) { }
        inline IntVector(int x, int y): x(x), y(y) { }
        inline IntVector(const Vector &v): x((int)roundf(v[0])), y((int)roundf(v[1])) { }
        inline int& operator[](int index)
        {
            return coords[index];
        }
        inline const int& operator[](int index) const
        {
            return coords[index];
        }
        inline bool operator == (const IntVector &other) const
        {
            return x == other.x && y == other.y;
        }
        inline bool operator != (const IntVector &other) const
        {
            return !(*this == other);
        }
        inline IntVector operator+ (const IntVector &other) const
        {
            return IntVector(x + other.x, y + other.y);
        }
        inline IntVector operator- (const IntVector &other) const
        {
            return IntVector(x - other.x, y - other.y);
        }

        inline Vector to_real() const
        {
            return Vector(Real(x), Real(y));
        }
        inline long long get_fixed_x_div_y()
        {
            return y == 0 ? 0 : int_to_fixed(x) / y;
        }
    };

    enum { FIXED_SHIFT = sizeof(int) * 8 };

    inline static long long int_to_fixed(int i)
    {
        return (long long)i << FIXED_SHIFT;
    }
    inline static int fixed_to_int(long long f)
    {
        return (int)(f >> FIXED_SHIFT);
    }

    inline static void norm_tex_coords(Vector &coords, const Vector &size)
    {
        if (coords[0] < 0.0 || coords[0] > size[0]) {
            coords[0] -= floor(coords[0] / size[0]) * size[0];
        }

        if (coords[1] < 0.0 || coords[1] > size[1]) {
            coords[1] -= floor(coords[1] / size[1]) * size[1];
        }
    }
};

void
TaskMeshSW::render_triangle(
    synfig::Surface &target_surface,
    const Vector &p0,
    const Vector &p1,
    const Vector &p2,
    const Color &color,
    Color::value_type opacity,
    Color::BlendMethod blend_method)
{
    // convert points to int
    Internal::IntVector ip0(p0), ip1(p1), ip2(p2);

    if (ip0 == ip1 || ip0 == ip2 || ip1 == ip2) {
        return;
    }

    if (ip0.x < 0 && ip1.x < 0 && ip2.x < 0) {
        return;
    }

    if (ip0.y < 0 && ip1.y < 0 && ip2.y < 0) {
        return;
    }

    int width = target_surface.get_w();
    int height = target_surface.get_h();

    if (width == 0 || height == 0) {
        return;
    }

    if (ip0.x >= width && ip1.x >= width && ip2.x >= width) {
        return;
    }

    if (ip0.y >= height && ip1.y >= height && ip2.y >= height) {
        return;
    }

    synfig::Surface::alpha_pen apen(target_surface.get_pen(0, 0));
    apen.set_alpha(opacity);
    apen.set_blend_method(blend_method);

    // sort points
    if (ip0.y > ip1.y) {
        std::swap(ip0, ip1);
    }

    if (ip0.y > ip2.y) {
        std::swap(ip0, ip2);
    }

    if (ip1.y > ip2.y) {
        std::swap(ip1, ip2);
    }

    // increments
    long long dx02 = (ip2 - ip0).get_fixed_x_div_y();
    long long dx01 = (ip1 - ip0).get_fixed_x_div_y();
    long long dx12 = (ip2 - ip1).get_fixed_x_div_y();

    // work points
    // initially at top point (p0)
    long long wx0 = Internal::int_to_fixed(ip0.x);
    long long wx1 = wx0;

    // process top part of triangle

    // make copy of dx02
    long long dx02_copy = dx02;

    // sort increments
    if (dx01 < dx02) {
        std::swap(dx02, dx01);
    }

    // rasterize
    for (int y = ip0.y; y < ip1.y; ++y) {
        // draw horizontal line (this code has a copy below)
        if (y >= 0 && y < height) {
            int x0 = Internal::fixed_to_int(wx0);
            int x1 = Internal::fixed_to_int(wx1);

            if (x0 < 0) {
                x0 = 0;
            }

            if (x1 >= width) {
                x1 = width - 1;
            }

            if (x1 >= x0) {
                apen.move_to(x0, y);

                for (int x = x0; x <= x1; ++x) {
                    apen.put_value(color);
                    apen.inc_x();
                }
            }
        }

        wx0 += dx02;
        wx1 += dx01;
    }

    if (ip0.y == ip1.y) {
        wx0 = Internal::int_to_fixed(ip0.x);
        wx1 = Internal::int_to_fixed(ip1.x);

        if (wx0 > wx1) {
            std::swap(wx0, wx1);
        }
    }

    // process bottom part of triangle

    // sort increments
    if (dx02_copy < dx12) {
        std::swap(dx02_copy, dx12);
    }

    // rasterize
    for (int y = ip1.y; y <= ip2.y; ++y) {
        // draw horizontal line (this code has a copy above)
        if (y >= 0 && y < height) {
            int x0 = Internal::fixed_to_int(wx0);
            int x1 = Internal::fixed_to_int(wx1);

            if (x0 < 0) {
                x0 = 0;
            }

            if (x1 >= width) {
                x1 = width - 1;
            }

            if (x1 >= x0) {
                apen.move_to(x0, y);

                for (int x = x0; x <= x1; ++x) {
                    apen.put_value(color);
                    apen.inc_x();
                }
            }
        }

        wx0 += dx02_copy;
        wx1 += dx12;
    }
}

void
TaskMeshSW::render_triangle(
    synfig::Surface &target_surface,
    const Vector &p0,
    const Vector &t0,
    const Vector &p1,
    const Vector &t1,
    const Vector &p2,
    const Vector &t2,
    const synfig::Surface &texture,
    Color::value_type opacity,
    Color::BlendMethod blend_method)
{
    if (t0[0] < 0.0 && t1[0] < 0.0 && t2[0] < 0.0) {
        return;
    }

    if (t0[1] < 0.0 && t1[1] < 0.0 && t2[1] < 0.0) {
        return;
    }

    // convert points to int
    Internal::IntVector ip0(p0), ip1(p1), ip2(p2);

    if (ip0 == ip1 || ip0 == ip2 || ip1 == ip2) {
        return;
    }

    if (ip0.x < 0 && ip1.x < 0 && ip2.x < 0) {
        return;
    }

    if (ip0.y < 0 && ip1.y < 0 && ip2.y < 0) {
        return;
    }

    int width = target_surface.get_w();
    int height = target_surface.get_h();

    if (width == 0 || height == 0) {
        return;
    }

    if (ip0.x >= width && ip1.x >= width && ip2.x >= width) {
        return;
    }

    if (ip0.y >= height && ip1.y >= height && ip2.y >= height) {
        return;
    }

    int tex_width = texture.get_w();
    int tex_height = texture.get_h();

    if (tex_width == 0 || tex_height == 0) {
        return;
    }

    Vector tex_size = Vector(Real(tex_width), Real(tex_height));

    if (t0[0] > tex_size[0] && t1[0] > tex_size[0] && t2[0] > tex_size[0]) {
        return;
    }

    if (t0[1] > tex_size[1] && t1[1] > tex_size[1] && t2[1] > tex_size[1]) {
        return;
    }

    // prepare texture matrix
    Matrix matrix_of_texture_triangle(
        t1[0] - t0[0], t1[1] - t0[1], 0.0,
        t2[0] - t0[0], t2[1] - t0[1], 0.0,
        t0[0], t0[1], 1.0);
    Matrix matrix_of_target_triangle(
        p1[0] - p0[0], p1[1] - p0[1], 0.0,
        p2[0] - p0[0], p2[1] - p0[1], 0.0,
        p0[0], p0[1], 1.0);
    matrix_of_target_triangle.invert();

    Matrix matrix = matrix_of_target_triangle * matrix_of_texture_triangle;
    Vector tdx = matrix.get_transformed(Vector(1.0, 0.0), false);
    // Vector tdy = matrix.get_transformed(Vector(0.0, 1.0), false);

    synfig::Surface::alpha_pen apen(target_surface.get_pen(0, 0));
    apen.set_alpha(opacity);
    apen.set_blend_method(blend_method);

    // sort points
    if (ip0.y > ip1.y) {
        std::swap(ip0, ip1);
    }

    if (ip0.y > ip2.y) {
        std::swap(ip0, ip2);
    }

    if (ip1.y > ip2.y) {
        std::swap(ip1, ip2);
    }

    // increments
    long long dx02 = (ip2 - ip0).get_fixed_x_div_y();
    long long dx01 = (ip1 - ip0).get_fixed_x_div_y();
    long long dx12 = (ip2 - ip1).get_fixed_x_div_y();

    // work points
    // initially at top point (p0)
    long long wx0 = Internal::int_to_fixed(ip0.x);
    long long wx1 = wx0;

    // process top part of triangle

    // make copy of dx02
    long long dx02_copy = dx02;

    // sort increments
    if (dx01 < dx02) {
        std::swap(dx02, dx01);
    }

    // rasterize
    for (int y = ip0.y; y < ip1.y; ++y) {
        // draw horizontal line (this code has a copy below)
        if (y >= 0 && y < height) {
            int x0 = Internal::fixed_to_int(wx0);
            int x1 = Internal::fixed_to_int(wx1);

            if (x0 < 0) {
                x0 = 0;
            }

            if (x1 >= width) {
                x1 = width - 1;
            }

            if (x1 >= x0) {
                apen.move_to(x0, y);
                Vector tex_point = matrix.get_transformed(Vector(Real(x0), Real(y)));

                for (int x = x0; x <= x1; ++x) {
                    if (tex_point[0] < 0.0 || tex_point[0] > tex_size[0]
                            || tex_point[1] < 0.0 || tex_point[1] > tex_size[1]) {
                        apen.set_alpha(0.0);
                        apen.put_value(Color());
                    } else {
                        apen.set_alpha(opacity);
                        apen.put_value(texture.cubic_sample(tex_point[0], tex_point[1]));
                    }

                    apen.inc_x();
                    tex_point += tdx;
                }
            }
        }

        wx0 += dx02;
        wx1 += dx01;
    }

    if (ip0.y == ip1.y) {
        wx0 = Internal::int_to_fixed(ip0.x);
        wx1 = Internal::int_to_fixed(ip1.x);

        if (wx0 > wx1) {
            std::swap(wx0, wx1);
        }
    }

    // process bottom part of triangle

    // sort increments
    if (dx02_copy < dx12) {
        std::swap(dx02_copy, dx12);
    }

    // rasterize
    for (int y = ip1.y; y <= ip2.y; ++y) {
        // draw horizontal line (this code has a copy above)
        if (y >= 0 && y < height) {
            int x0 = Internal::fixed_to_int(wx0);
            int x1 = Internal::fixed_to_int(wx1);

            if (x0 < 0) {
                x0 = 0;
            }

            if (x1 >= width) {
                x1 = width - 1;
            }

            if (x1 >= x0) {
                apen.move_to(x0, y);
                Vector tex_point = matrix.get_transformed(Vector(Real(x0), Real(y)));

                for (int x = x0; x <= x1; ++x) {
                    if (tex_point[0] < 0.0 || tex_point[0] > tex_size[0]
                            || tex_point[1] < 0.0 || tex_point[1] > tex_size[1]) {
                        apen.set_alpha(0.0);
                        apen.put_value(Color());
                    } else {
                        apen.set_alpha(opacity);
                        apen.put_value(texture.cubic_sample(tex_point[0], tex_point[1]));
                    }

                    apen.inc_x();
                    tex_point += tdx;
                }
            }
        }

        wx0 += dx02_copy;
        wx1 += dx12;
    }
}

void
TaskMeshSW::render_polygon(
    synfig::Surface &target_surface,
    const Vector *vertices,
    int vertices_strip,
    const int *triangles,
    int triangles_strip,
    int triangles_count,
    const Matrix &transform_matrix,
    const Color &color,
    Color::value_type opacity,
    Color::BlendMethod blend_method)
{
    if (!target_surface.is_valid()) {
        return;
    }

    if (vertices_strip <= 0) {
        vertices_strip = sizeof(Vector);
    }

    if (triangles_strip <= 0) {
        triangles_strip = sizeof(int[3]);
    }

    for (int i = 0; i < triangles_count; ++i) {
        int *triangle = (int*)((char*)triangles + i * triangles_strip);
        render_triangle(
            target_surface,
            transform_matrix.get_transformed(*(Vector*)((char*)vertices + triangle[0]*vertices_strip)),
            transform_matrix.get_transformed(*(Vector*)((char*)vertices + triangle[1]*vertices_strip)),
            transform_matrix.get_transformed(*(Vector*)((char*)vertices + triangle[2]*vertices_strip)),
            color,
            opacity,
            blend_method);
    }
}

void
TaskMeshSW::render_mesh(
    synfig::Surface &target_surface,
    const Vector *vertices,
    int vertices_strip,
    const Vector *tex_coords,
    int tex_coords_strip,
    const int *triangles,
    int triangles_strip,
    int triangles_count,
    const synfig::Surface &texture,
    const Matrix &transform_matrix,
    const Matrix &texture_matrix,
    Color::value_type opacity,
    Color::BlendMethod blend_method)
{
    if (!target_surface.is_valid()) {
        return;
    }

    if (!texture.is_valid()) {
        return;
    }

    if (vertices_strip <= 0) {
        vertices_strip = sizeof(Vector);
    }

    if (tex_coords_strip <= 0) {
        tex_coords_strip = sizeof(Vector);
    }

    if (triangles_strip <= 0) {
        triangles_strip = sizeof(int[3]);
    }

    for (int i = 0; i < triangles_count; ++i) {
        int *triangle = (int*)((char*)triangles + i * triangles_strip);
        render_triangle(
            target_surface,
            transform_matrix.get_transformed(*(Vector*)((char*)vertices + triangle[0]*vertices_strip)),
            texture_matrix.get_transformed(*(Vector*)((char*)tex_coords + triangle[0]*tex_coords_strip)),
            transform_matrix.get_transformed(*(Vector*)((char*)vertices + triangle[1]*vertices_strip)),
            texture_matrix.get_transformed(*(Vector*)((char*)tex_coords + triangle[1]*tex_coords_strip)),
            transform_matrix.get_transformed(*(Vector*)((char*)vertices + triangle[2]*vertices_strip)),
            texture_matrix.get_transformed(*(Vector*)((char*)tex_coords + triangle[2]*tex_coords_strip)),
            texture,
            opacity,
            blend_method);
    }
}

bool
TaskMeshSW::run(RunParams & /* params */) const
{
    synfig::Surface &a =
        SurfaceSW::Handle::cast_dynamic(target_surface)->get_surface();
    const synfig::Surface &b =
        SurfaceSW::Handle::cast_dynamic(sub_task()->target_surface)->get_surface();

    // TODO: target_rect

    Matrix3 transfromation_matrix;
    transfromation_matrix.m00 = get_units_per_pixel()[0];
    transfromation_matrix.m11 = get_units_per_pixel()[1];
    transfromation_matrix.m20 = get_source_rect_lt()[0];
    transfromation_matrix.m21 = get_source_rect_lt()[1];

    Matrix3 texture_transfromation_matrix;
    texture_transfromation_matrix.m00 = sub_task()->get_units_per_pixel()[0];
    texture_transfromation_matrix.m11 = sub_task()->get_units_per_pixel()[1];
    texture_transfromation_matrix.m20 = sub_task()->get_source_rect_lt()[0];
    texture_transfromation_matrix.m21 = sub_task()->get_source_rect_lt()[1];

    render_mesh(
        a,
        &mesh->vertices.front().position,
        sizeof(mesh->vertices.front()),
        &mesh->vertices.front().tex_coords,
        sizeof(mesh->vertices.front()),
        mesh->triangles.front().vertices,
        sizeof(mesh->triangles.front()),
        mesh->triangles.size(),
        b,
        transfromation_matrix,
        texture_transfromation_matrix,
        1.0,
        Color::BLEND_COMPOSITE);

    return true;
}